package util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import pojo.*;

/**
 * Utility class for parsing JSON strings to bean objects. 
 * @author zhixu
 *
 */
public class JsonParser {
	
	/**
	 * Convert a JSONArray to a List. Result is stored in the second parameter.
	 * @param jsonAray
	 * @param list The result List.
	 * @throws JSONException
	 */
	private void JsonArrayToList(JSONArray jsonAray, List<Object> list) throws JSONException{
		for (int i = 0; i < jsonAray.length(); i++)
			list.add(jsonAray.get(i));
	}
	
	/**
	 * Convert a JSONObject to a bean Object.
	 * When there is a List field in the bean object, this method can automatically parse it
	 * if the List parameter is an elementary type or its wrapped type or String type,
	 * otherwise it cannot, and you must apply this method on the List elements in a recursive style.
	 * @param jsonObj
	 * @param bean The result bean object.
	 * @return The result bean object.
	 */
	private Object JsonObjToBeanObj(JSONObject jsonObj, Object bean) {
		Field[] fields = bean.getClass().getDeclaredFields();
		for (Field field: fields) {
			if (!jsonObj.has(field.getName()))
				continue;
			field.setAccessible(true);
			try {
				String type = field.getType().getName();
				switch (type) {
				case "java.util.List":
					List<Object> list = new ArrayList<Object>();
					JsonArrayToList((JSONArray)jsonObj.get(field.getName()), list);
					field.set(bean, list);
					break;
				case "java.lang.String":
				case "java.lang.Integer":
				case "int":
				case "long":
				case "java.lang.Long":
					field.set(bean, jsonObj.get(field.getName()));
					break;
				default:
					Object obj = Class.forName(type).newInstance();
					JsonObjToBeanObj((JSONObject)jsonObj.get(field.getName()), obj);
					field.set(bean, obj);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return bean;
	}
	
	/**
	 * Get the "type" field of a JSON string.
	 * @param jsonStr
	 * @return a string of "type" field, or null if there is no "type" field.
	 * @throws JSONException
	 */
	public String typeOf(String jsonStr) throws JSONException{
		JSONObject jsonobj = new JSONObject(jsonStr);
		if (!jsonobj.has("type"))
			return null;
		
		String type = jsonobj.getString("type");
		return type;
	}
	
	/**
	 * Parse a JSON string to a User object.
	 * @param jsonStr
	 * @return a User object, or null if the JSON string is not of type "user"
	 * @throws JSONException
	 */
	public User parseUser(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"user".equals(type))
			return null;
		
		User resUser = new User();
		JsonObjToBeanObj(jsonObj.getJSONObject("user"), resUser);
		
		return resUser;
	}
	
	/**
	 * Parse a JSON string to a TimeStamps object
	 * @param jsonStr
	 * @return a TimeStamps object, or null if the JSON string is not of type "timeStamps"
	 * @throws JSONException
	 */
	public TimeStamps parseTimeStamps(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"timeStamps".equals(type))
			return null;
		
		TimeStamps resTimeStamps = new TimeStamps();
		JsonObjToBeanObj(jsonObj.getJSONObject("timeStamps"), resTimeStamps);
		
		return resTimeStamps;
	}
	
	/**
	 * Parse a JSON string to a SongList object
	 * @param jsonStr
	 * @return a SongList object, or null if the JSON string is not of type "songList"
	 * @throws JSONException
	 */
	public SongList parseSongList(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"songList".equals(type))
			return null;
		
		SongList resSongList = (SongList)JsonObjToBeanObj(jsonObj.getJSONObject("songList"), new SongList());
		return resSongList;
	}
	
	/**
	 * Parse a JSON string to a UpdatedData object.
	 * @param jsonStr
	 * @return a UpdatedData object, or null if the JSON string is not of type "updatedData"
	 * @throws JSONException
	 */
	public UpdatedData parseUpdatedData(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"updatedData".equals(type))
			return null;
				
		JSONObject updatedDataJO = jsonObj.getJSONObject("updatedData");
		JSONObject userJO = updatedDataJO.getJSONObject("user");
		JSONArray songListsJA = updatedDataJO.getJSONArray("songLists");
		JSONArray songsJA = updatedDataJO.getJSONArray("songs");
		
		UpdatedData resUpdatedData  = new UpdatedData();
		resUpdatedData.setUser((User)JsonObjToBeanObj(userJO ,new User()));
		resUpdatedData.setSongLists(new ArrayList<SongList>());
		resUpdatedData.setSongs(new ArrayList<Song>());
		for (int i = 0; i < songListsJA.length(); i++) {
			JSONObject songListJO = songListsJA.getJSONObject(i);
			SongList songList = (SongList)JsonObjToBeanObj(songListJO, new SongList());
			resUpdatedData.getSongLists().add(songList);
		}
		for (int i = 0; i < songsJA.length(); i++) {
			JSONObject songJO = songsJA.getJSONObject(i);
			Song song = (Song)JsonObjToBeanObj(songJO, new Song());
			resUpdatedData.getSongs().add(song);
		}
		
		return resUpdatedData;
	}
	
	/**
	 * Parse a JSON string to a Comment object.
	 * @param jsonStr
	 * @return a Comment object, or null if the JSON string is not of type "comment"
	 * @throws JSONException
	 */
	public Comment parseComment(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"comment".equals(type))
			return null;
		
		Comment resComment = (Comment)JsonObjToBeanObj(jsonObj.getJSONObject("comment"), new Comment());
		return resComment;
	}
	
	/**
	 * Parse a JSON string to an Artist object.
	 * @param jsonStr
	 * @return an Artist object, or null if the JSON string is not of type "artist"
	 * @throws JSONException
	 */
	public Artist parseArtist(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"artist".equals(type))
			return null;
		
		Artist resArtist = (Artist)JsonObjToBeanObj(jsonObj.getJSONObject("artist"), new Artist());
		return resArtist;
	}
	
	/**
	 * Parse a JSON string to an Album object.
	 * @param jsonStr
	 * @return an Album object, or null if the JSON string is not of type "artist" 
	 * @throws JSONException
	 */
	public Album parseAlbum(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"album".equals(type))
			return null;
		
		Album resAlbum = (Album)JsonObjToBeanObj(jsonObj.getJSONObject("album"), new Album());
		return resAlbum;
	}
	
	/**
	 * Parse a JSON string to a Comment List.
	 * @param jsonStr
	 * @return a List<Comment>, or null if the JSON string is not of type "comments"
	 * @throws JSONException
	 */
	public List<Comment> parseComments(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"comments".equals(type))
			return null;
		
		JSONArray commentJA = jsonObj.getJSONArray("comments");
		List<Comment> resComments = new ArrayList<Comment>();
		
		for (int i = 0; i < commentJA.length(); i++) {
			JSONObject commentJO = commentJA.getJSONObject(i);
			Comment comment = (Comment)JsonObjToBeanObj(commentJO, new Comment());
			resComments.add(comment);
		}
		
		return resComments;
	}
	
	/**
	 * Parse a JSON string to a Song List.
	 * @param jsonStr
	 * @return a List<Song>, or null if the JSON string is not of type "songs" 
	 * @throws JSONException
	 */
	public List<Song> parseSongs(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"songs".equals(type))
			return null;
			
		JSONArray songsJA = jsonObj.getJSONArray("songs");
		List<Song> resSongs = new ArrayList<Song>();
		
		for (int i = 0; i < songsJA.length(); i++) {
			JSONObject songJO = songsJA.getJSONObject(i);
			Song song = (Song)JsonObjToBeanObj(songJO, new Song());
			resSongs.add(song);
		}
		
		return resSongs;
	}
	
	/**
	 * Parse a JSON string to a ValidInfo message string 
	 * @param jsonStr
	 * @return a String, or null if the JSON string is not of type "validInfo"
	 * @throws JSONException
	 */
	public String parseValidInfo(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"validInfo".equals(type))
			return null;
		
		return jsonObj.getString("msg");
	}
	
	/**
	 * Parse a JSON string to a InvalidInfo message string
	 * @param jsonStr
	 * @return a String, or null if the JSON string is not of type "invalidInfo"
	 * @throws JSONException
	 */
	public String parseInvalidInfo(String jsonStr) throws JSONException{
		JSONObject jsonObj = new JSONObject(jsonStr);
		String type = jsonObj.getString("type");
		if (!"invalidInfo".equals(type))
			return null;
		
		return jsonObj.getString("msg");
	}
}
